package glassline;

import glassline.Glass.WorkStation;
import glassline.interfaces.Conveyor;
import glassline.interfaces.Sensor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import transducer.TChannel;
import transducer.TEvent;
import transducer.Transducer;
import agent.Agent;

public class ConveyorAgent extends Agent implements Conveyor {
	enum ConveyorState {
		Off,
		Starting,
		On,
		Stopping,
	}
	
	public ConveyorState conveyorState = ConveyorState.Off;
	
	public Conveyor next_obj;
	public Conveyor prev_obj;
	public boolean nextObjConveying;
	public boolean prevObjConveying;
	
	public class MyGlass {
		public Glass g;
		public Sensor lastSensor;
		
		public MyGlass(Glass g) {
			this.g = g;
			lastSensor = null;
		}
	}
	
	public List<MyGlass> myGlass = Collections.synchronizedList(new ArrayList<MyGlass>());
	boolean passLastGlass = false;
	
	public enum SensorType {
		Begin,
		End,
		PrePopup,
		Popup,
	}

	class MySensor {
		Sensor sensor;
		boolean sensing = false; // If the sensor is currently sensing something or not.
		Map<SensorType, Boolean> type = new HashMap<SensorType, Boolean>();

		MySensor(Sensor s, boolean begin, boolean end, boolean prepopup, boolean popup) {
			sensor = s;

			type.put(SensorType.Begin, begin);
			type.put(SensorType.End, end);
			type.put(SensorType.PrePopup, prepopup);
			type.put(SensorType.Popup, popup);
		}
	}

	PopupAgent popup; // Should only be 1 popup per conveyor (null if no popup)
	PopupState pstate = PopupState.Down; // State of the popup on this conveyor (if there is one)
	List<MySensor> sensors = Collections.synchronizedList(new ArrayList<MySensor>()); // List of sensors on this conveyor
	List<MySensor> sensorHits = Collections.synchronizedList(new ArrayList<MySensor>()); // List of unprocessed sensor state changes.
	Map<SensorType, Boolean> nextConveyorSensorInfo = new HashMap<SensorType, Boolean>(); // Stores the last known state of the conveyor sensors ahead of this one.

	public ConveyorAgent(Transducer t, Conveyor next_obj, Conveyor prev_obj) {
		super(t);
		this.next_obj = next_obj;
		this.prev_obj = prev_obj;
	}
	
	// Messages
	
	/* (non-Javadoc)
	 * @see glassline.Conveyor#msgHereIsGlass(glassline.Glass)
	 */
	@Override
	public void msgHereIsGlass(Glass g) {
		myGlass.add(new MyGlass(g));
		//stateChanged();
	}
	
	public void msgPassGlassOn() {
		passLastGlass = true;
		stateChanged();
	}
	
	public void msgStartConveyor() {
		conveyorState = ConveyorState.Starting;
		stateChanged();
	}

	public void msgStopConveyor() {
		conveyorState = ConveyorState.Stopping;
		stateChanged();
	}

	public void msgImConveying(Conveyor obj) { // Sent from nearby conveyor
		if (obj == next_obj) nextObjConveying = true;
		else if (obj == prev_obj) prevObjConveying = true;
		stateChanged();
	}

	public void msgImStopping(Conveyor obj) { // Sent from nearby factory object
		if (obj == next_obj) nextObjConveying = false;
		else if (obj == prev_obj) prevObjConveying = false;
		stateChanged();
	}

	/* (non-Javadoc)
	 * @see glassline.Conveyor#msgSensorNotify(glassline.Sensor, boolean)
	 */
	@Override
	public void msgSensorNotify(Sensor sensor, boolean sensing) { // Received from a sensor that just sensed something.
		for (MySensor s : sensors) {
			if (sensor == s.sensor) {
				s.sensing = sensing;
				sensorHits.add(s);
				
				if (sensing) {
					// Store this sensor as the last sensor this glass touched.
					int sensorIndex = sensors.indexOf(s);
					boolean failsafe = false;
					
					// Check if this glass hit the first sensor.
					if (sensorIndex == 0) {
						for (MyGlass myG : myGlass) {
							if (myG.lastSensor == null) {
								myG.lastSensor = sensor;
								failsafe = true;
								break;
							}
						}
					}
					else if (sensorIndex > 0) {
						for (int i = myGlass.size() - 1; i >= 0; i--) {
							MyGlass myG = myGlass.get(i);
							if (myG.lastSensor == sensors.get(sensorIndex - 1).sensor) {
								myG.lastSensor = sensor;
								failsafe = true;
								break;
							}
						}
					}
					
					if (!failsafe) {
						System.out.println("ERROR: Didn't correctly determine which glass tripped sensor: " + sensor);
					}
				}
				
				break;
			}
		}
		
		stateChanged();
	}

	/* (non-Javadoc)
	 * @see glassline.Conveyor#msgNextSensorNotify(glassline.Sensor, boolean)
	 */
	@Override
	public void msgNextSensorNotify(Conveyor conveyor, Sensor sensor, boolean sensing) { // Received from sensors on the next conveyor.
		// TODO: check this
		for (MySensor s : sensors) {
			if (sensor == s.sensor) {
				// Update all entries that match this sensor.
				for (Map.Entry<SensorType, Boolean> type : s.type.entrySet()) {
					nextConveyorSensorInfo.put(type.getKey(), sensing);
				}
				stateChanged();
			}
		}
	}

	/* (non-Javadoc)
	 * @see glassline.Conveyor#pickAndExecuteAnAction()
	 */
	@Override
	public boolean pickAndExecuteAnAction() {
		if (conveyorState == ConveyorState.Starting) {
			Start();
			return true;
		}
	
		// Rule #2
		if (conveyorState == ConveyorState.Stopping) {
			Stop();
			return true;
		}
		
		// Rule #3
		if (passLastGlass) {
			passLastGlass = false;
			if (myGlass.isEmpty()) { // Error checking
				System.out.println("ERROR: Tried passing a piece of glass from a station with no glass");
				return true;
			}
			Glass g = myGlass.get(0).g; // Pop first glass off.
			myGlass.remove(0);
			if (next_obj != null) next_obj.msgHereIsGlass(g);
			else System.out.println("There is no next station for this factory agent, so the glass reference was lost.");
			return true;
		}
		
		// CONVEYOR STOP LOGIC
		
		// Get the hit sensor's type.
		if (!sensorHits.isEmpty()) {
			MySensor sensor = sensorHits.get(0);
			sensorHits.remove(0);

			boolean begin = isSensorOfType(sensor, SensorType.Begin);
			boolean end = isSensorOfType(sensor, SensorType.End);
			boolean prepopup = isSensorOfType(sensor, SensorType.PrePopup);
			boolean popup = isSensorOfType(sensor, SensorType.Popup);

			/*
			MySensor begin_sensor = getSensorOfType(SensorType.Begin);
			MySensor end_sensor = getSensorOfType(SensorType.End);
			MySensor prepopup_sensor = getSensorOfType(SensorType.PrePopup);
			MySensor popup_sensor = getSensorOfType(SensorType.Popup);
			 */
			
			// Determine which glass tripped this conveyor.
			Glass tripper = null;
			for (MyGlass myG : myGlass) {
				if (myG.lastSensor == sensor.sensor) tripper = myG.g;
			}
			if (tripper == null) {
				System.out.println("ERROR: Couldn't determine what glass tripped this sensor in Conveyor scheduler.");
				return true;
			}
			
			// If the end sensor of this conveyor was just tripped on or off and the next conveyor is off, stop this conveyor to prevent a crash.
			if (end && !nextObjConveying) {
				Stop();
				return true;
			}

			if (sensor.sensing) { // A sensor just started sensing an object on it
				if (begin && prepopup) { // Not sure if this is ever happens?
				}

				// If the popup sensor just got tripped then raise it if the glass requires it.
				else if (popup) {
					// If glass should stop here, then raise popup.
					// TODO: Hardcoded CrossSeamer for testing
					if (tripper.recipe.workstations.containsKey(WorkStation.CrossSeamer)) {
						tellPopupGoUp();
					}

					return true;
				}

				// If the end sensor on this conveyor acts as the prepopup for the popup on the next conveyor, then check if the popup sensor is sensing glass and stop the conveyor if so.
				else if (end && prepopup) {
					if (nextConveyorSensorInfo.get(SensorType.Popup)) {
						Stop();
					}
				}

				// Not sure what to do with this.. seems useless unless theres a popup here which is already handled above.  Otherwise there is guaranteed to be a pre popup sensor before the next popup so what is this for?
				else if (begin) {
				}


				else if (end) {
				}

				else if (prepopup) {
					if (pstate == PopupState.Up) {
						// SHUT DOWN THIS CONVEYOR
						Stop();
					}
				}
			}

			return true;
		}

		return false;
	}

	/* (non-Javadoc)
	 * @see glassline.Conveyor#eventFired(transducer.TChannel, transducer.TEvent, java.lang.Object[])
	 */

	public void eventFired(TChannel channel, TEvent event, Object[] args) {
	}
	
	// Actions

	protected void Start() {
		conveyorState = ConveyorState.On;
		if (prev_obj != null) prev_obj.msgImConveying(this);
		if (next_obj != null) next_obj.msgImConveying(this);
	}

	protected void Stop() {
		conveyorState = ConveyorState.Off;
		if (prev_obj != null) prev_obj.msgImStopping(this);
		if (next_obj != null) next_obj.msgImStopping(this);
	}
	
	// Wrapper functions
	public Conveyor getNextObj() { return next_obj; }
	public Conveyor getPrevObj() { return prev_obj; }
	public boolean isNextObjConveying() { return nextObjConveying; }
	public boolean isPrevObjConveying() { return prevObjConveying; }
	
	// Other
	
	public void setPopup(PopupAgent p) {
		popup = p;
	}
	
	public void setNextConv(Conveyor c) {
		next_obj = c;
	}
	
	public void setPrevConv(Conveyor c) {
		prev_obj = c;
	}
	
	public void addSensor(Sensor sensor, boolean begin, boolean end, boolean prepopup, boolean popup) {
		sensors.add(new MySensor(sensor, begin, end, prepopup, popup));
		sensor.setParent(this);
		if (prev_obj != null) sensor.setParentPrevNeighbor(prev_obj);
	}

	public boolean isSensorOfType(MySensor sensor, SensorType t) {
		return sensors.get(sensors.indexOf(sensor)).type.get(t);
	}
	
	// Unit test stufffff
	
	/* (non-Javadoc)
	 * @see glassline.Conveyor#printData()
	 */
	@Override
	public void printData() {
		System.out.println("--------------------");
		System.out.println("Conveyor state: " + conveyorState);
		System.out.println("next obj: " + next_obj);
		System.out.println("prev_obj: " + prev_obj);
		System.out.println("nextObjConveying: " + nextObjConveying);
		System.out.println("prevObjConveying: " + prevObjConveying);
		System.out.println("myGlass: " + myGlass.toString());
		System.out.println("Popup: " + popup);
		System.out.println("popup state: " + pstate);
		System.out.println("Sensors: " + sensors.toString());
		System.out.println("sensorHits: " + sensorHits.toString());
		System.out.println("nextConveyorSensorInfo: " + nextConveyorSensorInfo.toString());
	}
	
	private MySensor getSensorOfType(SensorType t) {
		for (MySensor s : sensors) {
			if (s.type.get(t)) return s;
		}
		return null;
	}
	
	private void tellPopupGoUp() {
		popup.msgPopUp();
		pstate = PopupState.Up;
	}

	private void tellPopupGoDown() {
		popup.msgPopDown();
		pstate = PopupState.Down;
	}
}
